import { ColliderType, GameEvent, SysOrder } from "../../GameDef";
import { QuadTree } from "../../lib/kgame/d2/QuadTree";
import { Rect } from "../../lib/kgame/d2/math/Rect";
import { Vec2 } from "../../lib/kgame/d2/math/Vec2";
import { IECSFilter } from "../../lib/kgame/ecs/ECSFilter";
import { ECSSys } from "../../lib/kgame/ecs/ECSSys";
import { FoodCmp } from "../cmp/FoodCmp";
import { SnakeBodyCmp } from "../cmp/SnakeBodyCmp";
import { SnakeCmp } from "../cmp/SnakeCmp";
import { SnakeHeadCmp } from "../cmp/SnakeHeadCmp";
import { FoodFactory } from "../factory/FoodFactory";
import { SnakeFactory } from "../factory/SnakeFactory";
import { ColliderMgr } from "../scmp/ColliderMgr";
import { WorldMgr } from "../scmp/WorldMgr";

const spawnPos = new Vec2();
const spawnRect = new Rect();

/**
 * 蛇系统
 */
export class SnakeSys extends ECSSys {

    get updateOrder(): number {
        return SysOrder.snake;
    }

    get ifilter(): IECSFilter {
        return { includeOne: [SnakeCmp, SnakeHeadCmp] };
    }

    onBeforeUpdate(): void {
        this.getCmps(SnakeCmp).forEach(snake => {
            if (this.checkRemove(snake)) {
                return;
            }
            this.checkSpawn(snake);
        });

        this.getCmps(SnakeHeadCmp).forEach(head => {
            if (head.move.isSprint) {
                //冲刺消耗分数
                const cost = 20 + head.score / 200;
                const score = cost * this.scene.dt;
                if (head.score > score) {
                    head.score -= score;
                    head.sprintScore += score;
                    if (head.sprintScore >= cost / 10) {
                        const randDis = head.radius / 2;
                        spawnPos.setPos(
                            head.tail.pos.x + this.scene.rand.shake(randDis),
                            head.tail.pos.y + this.scene.rand.shake(randDis),
                        )
                        const lifeTime = this.scene.rand.range(3, 6);
                        const color = this.scene.rand.choose(head.colors);
                        FoodFactory.createFood(this.scene, spawnPos, Math.min(5, head.sprintScore / 2), lifeTime, color);
                        head.sprintScore = 0;
                    }
                } else {
                    //分数不够停止
                    head.move.isSprint = false;
                }
            }
            head.updateAttr();
            this.updateGhost(head);
        });
    }

    onUpdate(): void {
        this.getCmps(SnakeHeadCmp).forEach(head => {
            head.entity.rot = head.move.moveDir;
            this.recordHeadPath(head);
            this.moveBody(head);
            this.updateBodyLen(head);
        });
    }

    onLateUpdate(): void {
        this.getCmps(SnakeHeadCmp).forEach(head => {
            this.checkDead(head);
        });
    }

    /**
     * 生成蛇
     * @param snake 
     */
    spawn(snake: SnakeCmp) {
        const { initLen, initSize } = XSnakeMisc;
        const wrold = this.scene.getCmp(WorldMgr);
        const safeDis = initSize * initLen + 25;

        //优先从四叉树从中找出没有蛇的地方
        const snakeTree = this.scene.getCmp(ColliderMgr).containerMap.get(ColliderType.snake) as QuadTree;
        const emptyNodes = snakeTree.getEmptyNodes();
        if (emptyNodes.length > 0) {
            const node = this.scene.rand.choose(emptyNodes);
            spawnRect.setRectTo(node.rect).intersectionRect(
                Rect.T.setRect(
                    wrold.rect.x + safeDis,
                    wrold.rect.y + safeDis,
                    wrold.rect.width - safeDis,
                    wrold.rect.height - safeDis
                ));
            spawnPos.setPos(
                spawnRect.x + this.scene.rand.rand() * spawnRect.width,
                spawnRect.y + this.scene.rand.rand() * spawnRect.height
            );
        } else {
            wrold.getRandPos(safeDis, spawnPos);
        }

        snake.pos.setPosTo(spawnPos);
        snake.rot = this.scene.rand.randRad();
        const head = snake.head = SnakeFactory.createHead(snake);
        for (let i = 0; i < initLen; ++i) {
            SnakeFactory.createBody(head, spawnPos.moveRad(snake.rot, -head.space * i));
        }
        this.scene.send(GameEvent.snakeSpawn, snake);
    }

    /**
     * 检测是否移除蛇
     * @param snake 
     */
    checkRemove(snake: SnakeCmp) {
        if (snake.doRemove) {
            snake.doRemove = false;
            if (snake.head) {
                this.dead(snake.head, null);
            }
            snake.entity.removeSelf();
            this.scene.send(GameEvent.snakeRemove, snake.snakeId);
            return true;
        }
        return false;
    }

    /**
     * 检测生成
     * @param snake
     */
    checkSpawn(snake: SnakeCmp) {
        if (snake.death) {
            snake.respawnCd -= this.scene.dt;
            if (snake.respawnCd <= 0) {
                this.spawn(snake);
            }
        }
    }

    /**
     * 更新幽灵状态
     * @param head
     */
    updateGhost(head: SnakeHeadCmp) {
        if (head.ghostTime > 0) {
            head.ghostTime -= this.scene.dt;
            if (head.ghostTime <= 0) {
                head.ghostTime = 0;
                head.snakeCollider.enable = true;
                head.bodys.forEach(body => {
                    body.collider.enable = true;
                });
                this.scene.send(GameEvent.snakeGhost, head.snake);
            }
        }
    }

    /**
     * 记录蛇头路径点
     * @param head
     */
    recordHeadPath(head: SnakeHeadCmp) {
        const { pos, rot } = head.entity;
        let path = head.pathRecy;
        if (path) {
            head.pathRecy = path.next;
            path.rot = rot;
            Vec2.setPosTo(path, pos);
        } else {
            path = { x: pos.x, y: pos.y, rot };
        }
        path.next = head.path;
        head.path = path;

        head.snake.pos.setPosTo(pos);
        head.snake.rot = rot;
    }

    /**
     * 根据蛇头路径移动蛇身
     * @param head
     */
    moveBody(head: SnakeHeadCmp) {
        let path = head.path.next;
        head.bodys.some((body, idx) => {
            if (idx > 0) {
                const last = head.bodys[idx - 1];
                while (path) {
                    const dis = last.pos.dis(path);
                    const space = dis - head.space;
                    if (space > 0) {
                        body.pos.setPosTo(path);
                        body.entity.rot = path.rot;
                        body.pos.moveRad(path.rot, space);
                        break;
                    }
                    path = path.next;
                }
                return !path;
            }
        });

        if (path) {
            head.pathTail = path;
            const recy = path.next;
            if (recy) {
                //回收已不需要的路径
                head.pathTail.next = null;
                let tail = recy;
                while (tail.next) {
                    tail = tail.next;
                }
                tail.next = head.pathRecy;
                head.pathRecy = recy;
            }
        }

        //更新碰撞器
        head.foodCollider.shape.update(head.pos);
        head.snakeCollider.shape.update(head.pos);
        head.bodys.forEach(body => {
            body.collider.shape.radius = head.radius;
            body.collider.shape.update(body.pos);
        });
    }

    /**
     * 更新蛇身长度
     * @param head
     */
    updateBodyLen(head: SnakeHeadCmp) {
        let grow = head.len - head.bodys.length;
        while (grow > 0) {
            //变长
            --grow;
            SnakeFactory.createBody(head);
        }
        while (grow < 0) {
            //缩短
            ++grow;
            head.bodys.pop().entity.removeSelf();
        }
    }

    /**
     * 检测碰撞死亡
     * @param head
     */
    checkDead(head: SnakeHeadCmp) {
        const killer = head.snakeCollider.collidersOther[0];
        if (killer) {
            this.dead(head, killer.getCmp(SnakeBodyCmp).head.snake);
        } else {
            const world = this.scene.getCmp(WorldMgr);
            if (world.isOutEdge(head.snakeCollider.shape.worldAABB)) {
                this.dead(head, "edge");
            }
        }
    }

    /**
     * 死亡
     * @param head
     * @param killer
     */
    dead(head: SnakeHeadCmp, killer: SnakeCmp | string) {
        const { snake } = head;
        snake.killer = killer;
        snake.respawnCd = 2;
        const oneScore = 5 + head.score / 3 / head.len;
        head.bodys.forEach(body => {
            const score = oneScore * this.scene.rand.range(0.9, 1.1);
            const randDis = FoodCmp.getSize(score) / 2;
            spawnPos.setPos(
                body.pos.x + this.scene.rand.shake(randDis),
                body.pos.y + this.scene.rand.shake(randDis)
            )
            const lifeTime = this.scene.rand.range(10, 30);
            const color = head.colors[body.idx % head.colors.length];
            FoodFactory.createFood(this.scene, spawnPos, score, lifeTime, color);
            body.entity.removeSelf();
        });
        snake.head = null;
        if (killer) {
            this.scene.send(GameEvent.snakeDead, snake);
        }
    }

}


declare global {

    interface ECSEventData {
        [GameEvent.snakeSpawn]: SnakeCmp,
        [GameEvent.snakeDead]: SnakeCmp
        [GameEvent.snakeGhost]: SnakeCmp
    }

}